Skip to content

Add a report-only style-compatibility preflight and the applicable-slot matrix - #452

Closed
Alek99 wants to merge 2 commits into
mainfrom
alek/compat-1-preflight
Closed

Add a report-only style-compatibility preflight and the applicable-slot matrix#452
Alek99 wants to merge 2 commits into
mainfrom
alek/compat-1-preflight

Conversation

@Alek99

@Alek99 Alek99 commented Aug 4, 2026

Copy link
Copy Markdown
Member

First change of the styling-compatibility program (Phase 0 of the plan): make every export explain itself before changing anything about how exports behave.

What this adds

  • chart.style_compatibility_report(target=..., engine=..., custom_css=...) — a report-only preflight that routes every declared slot style for one export target into exactly one of four outcomes: survives, native-subset (naming the kept and lost properties per format family), browser-only, or state-gated. It mirrors the export path's own refusals (custom_css with a pinned native engine, Chromium SVG) by calling the same resolver, so it can never disagree with what running the export would do. Report-only: computing it changes no export. The staged compatibility= modes that act on the report come in the follow-up PR.
  • The applicable-slot matrix. The capability registry now tags each of the 48 chrome slots with an applicability — present in a clean static export (24), or gated by an export state: hover (5), modebar (14), crosshair (2), selection (1), view/reduction badges (2). The generated matrices gain the applicable in column and applicable-slot counts. A styled tooltip is no longer counted as "dropped" by a static PNG that never contains a tooltip — recorded with its gating state instead (§28: the distinction is written down, not silent).

Contract properties (tested)

  • Constant-time when there is nothing to route: no class_names, no per-slot styles, no custom_css → no slot walk. Preflight stays free exactly where exports are hot.
  • One source of truth per fact: routing derives from the capability registry, honored property subsets from the writers' own constants (_svg.SLOT_TEXT_PROPS / SLOT_RASTER_PROPS), engine selection from export._resolve_image_engine. The preflight restates none of them.
  • Partition is pinned: new registry tests fail if a future modebar_*/tooltip*/crosshair_*/badge* slot forgets its family state, and reject native support claims on state-gated slots until the interaction-snapshot phase adds them deliberately.
  • The legend slot is qualified rather than guessed in either direction (its box properties route through the merged legend declaration, which has no constant yet — the shared-IR PR makes it exact).

Evidence

  • 20 new preflight tests + 4 new registry tests; full suite: 3973 passed, 109 skipped.
  • spec/api/export.md §9 gains the report as its programmatic form; spec/api/capability-matrix.md and docs/styling/capabilities.md regenerated (the --check drift gate covers both).
  • ruff check / ruff format --check / pre-commit: clean. ty check: no diagnostics in the new code (baseline count unchanged).

Part of the full styling-compatibility plan (resolved-style IR → live snapshot → native chrome parity); next in the stack: opt-in compatibility= warn/strict modes and the engine-collision contract.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Added style_compatibility_report() for charts and figures to preview how styling will behave for a selected export format and engine.
    • Reports identify styles that survive export, are partially supported, browser-only, or dependent on interaction and view state.
    • Export compatibility checks now include custom CSS and clearly explain unsupported styles and export refusals.
  • Documentation

    • Updated capability references to distinguish static-export features from state-dependent chart features, including applicability details for all styling slots.

…ot matrix

chart.style_compatibility_report(target=..., engine=..., custom_css=...)
routes every declared slot style for one export target into exactly one of
four outcomes — survives, native-subset (naming the kept and lost properties
per format family), browser-only, or state-gated — and mirrors the export
path's own refusals (custom_css with a pinned native engine, Chromium SVG)
instead of re-deciding them. Report-only: computing it never changes an
export; the staged compatibility= modes that act on it come separately.

Two properties are load-bearing. The report is constant-time when there is
nothing to route: no class_names, no per-slot styles, no custom_css means no
slot walk, so preflight stays free exactly where exports are hot. And
state-gated is not lost: the capability registry now tags every slot with an
applicability — present in a clean static export, or gated by hover,
selection, crosshair, modebar, or view (reduction badges) — so a styled
tooltip is recorded with its gating state rather than counted against a file
that never contains a tooltip. Counting it before this change overstated the
static parity gap by exactly the chrome a static file cannot contain: of 48
slots, 24 are clean-static and 24 state-gated, and all 10 native-capable
slots are in the static set.

Routing derives from the capability registry, the honored property subsets
from the writers' own constants (xy._svg.SLOT_TEXT_PROPS / SLOT_RASTER_PROPS),
and engine selection from export._resolve_image_engine — the preflight
restates none of them, so it cannot disagree with them. The legend slot stays
at declaration granularity (its box properties route through the merged
legend declaration, which has no constant yet) and is qualified rather than
guessed either direction (§28).

The generated capability matrices gain the applicable-in column and the
applicable-slot counts; new registry tests pin the partition (a new
modebar_*/tooltip/crosshair/badge slot that forgets its family state fails
the suite, and native support on a state-gated slot is rejected until the
interaction-snapshot phase adds it deliberately). export.md §9 documents the
report as its programmatic form.
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Alek99, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 2 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d42f81e2-9301-4174-9b8e-2c4550939781

📥 Commits

Reviewing files that changed from the base of the PR and between a99c037 and f661794.

📒 Files selected for processing (7)
  • python/xy/_figure.py
  • python/xy/components.py
  • python/xy/styling/capabilities.py
  • python/xy/styling/preflight.py
  • scripts/gen_capability_matrix.py
  • spec/api/capability-matrix.md
  • tests/test_style_compatibility_report.py
📝 Walkthrough

Walkthrough

The change adds export applicability metadata for chart slots and introduces report-only style compatibility analysis. Figure and Chart expose the report API. Documentation and tests cover routing, state-gated slots, export refusals, and capability counts.

Changes

Style compatibility reporting

Layer / File(s) Summary
Export applicability registry
python/xy/styling/capabilities.py, tests/test_capability_registry.py
The registry classifies slots as static or state-gated, reports applicability counts, and validates state mappings and native support.
Compatibility preflight engine
python/xy/styling/preflight.py
The new preflight implementation resolves export settings and routes declared styles to surviving, native-subset, browser-only, or state-gated outcomes.
Public API and validation
python/xy/styling/__init__.py, python/xy/_figure.py, python/xy/components.py, tests/test_style_compatibility_report.py, spec/api/export.md
Figure and Chart expose report-only compatibility analysis. Tests and API documentation cover routing, refusals, fast paths, explanations, and parity.
Capability matrix documentation
docs/styling/capabilities.md, scripts/gen_capability_matrix.py, spec/api/capability-matrix.md, CHANGELOG.md
The capability matrices and changelog document static and state-gated slot counts, applicability states, and target-specific compatibility reporting.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Chart
  participant Figure
  participant Preflight
  participant ExportResolver
  participant Report
  Chart->>Figure: request style_compatibility_report(target, engine, custom_css)
  Figure->>Preflight: analyze figure styles
  Preflight->>ExportResolver: resolve target and engine
  ExportResolver-->>Preflight: return writer capabilities and refusals
  Preflight->>Report: classify style routes and findings
  Report-->>Figure: return compatibility report
  Figure-->>Chart: return report without export
Loading

Possibly related PRs

  • reflex-dev/xy#310: Extends the capability registry with export-state applicability metadata and preflight reporting.
  • reflex-dev/xy#311: Builds on DOM slot and styling capabilities for export applicability and preflight routing.
  • reflex-dev/xy#325: Relates to the capability registry and static versus state-gated style support.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies both primary changes: the report-only style-compatibility preflight and the applicable-slot matrix.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch alek/compat-1-preflight

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds a report-only styling compatibility preflight and records whether each chrome slot applies to static exports or requires interaction/view state.

  • Exposes style_compatibility_report() through both Chart and Figure.
  • Routes declared slot styles using the capability registry, native writer property subsets, and export engine resolver.
  • Extends generated capability matrices with slot applicability and static/state-gated counts.
  • Adds registry and preflight contract tests, including browser custom-CSS refusal parity.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
python/xy/styling/preflight.py Adds compatibility report data structures and routing logic; the previously reported custom-CSS validation bypass is fixed by invoking the export path’s validator.
python/xy/styling/capabilities.py Adds explicit static and state-gated applicability metadata to chrome slot capabilities.
python/xy/_figure.py Adds the Figure-level compatibility-report entry point with a deferred import.
python/xy/components.py Adds the public Chart wrapper for compatibility reporting.
tests/test_style_compatibility_report.py Covers routing, writer subsets, state-gated slots, engine refusals, malformed styling, and custom-CSS validation parity.
tests/test_capability_registry.py Extends registry invariants to pin applicability coverage and state-family assignments.

Reviews (2): Last reviewed commit: "Close the preflight report's two mirror ..." | Re-trigger Greptile

Comment thread python/xy/styling/preflight.py
@codspeed-hq

codspeed-hq Bot commented Aug 4, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 109 untouched benchmarks
⏩ 2 skipped benchmarks1


Comparing alek/compat-1-preflight (f661794) with main (99eda6d)

Open in CodSpeed

Footnotes

  1. 2 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (5)
scripts/gen_capability_matrix.py (1)

66-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the native slot-style count from the registry.

counts['slots_styleable_natively'] is dynamic, but Line 69 hard-codes nine and the root exception. If a slot changes its channel or native support, the generated summary can become stale while the table and total remain current. Add a registry-derived count for the styles={...} path, or remove the numeric split. The generated spec/api/capability-matrix.md summary inherits this literal.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/gen_capability_matrix.py` around lines 66 - 74, Update the summary
generation near counts['slots_styleable_natively'] to derive the styles={...}
slot count from the capability registry instead of hard-coding “nine” and the
root exception. Reuse the registry’s native-support/channel data so the
generated prose stays synchronized with the table and total when slots change.
python/xy/styling/preflight.py (3)

42-43: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Classify the writer family from an explicit format set.

Line 183 treats every non-raster format as vector, and _VECTOR_FORMATS is never read. A format added later to export._normalize_format would silently be reported against the vector subset. Select the family from both sets and refuse an unclassified format.

♻️ Proposed change
-def _styles_finding(slot: str, decls: dict[str, Any], fmt: str) -> SlotFinding:
-    meta = _SLOTS_BY_ID[slot]
-    family = "native_raster" if fmt in _RASTER_FORMATS else "native_vector"
+def _slot_family(fmt: str) -> str:
+    if fmt in _RASTER_FORMATS:
+        return "native_raster"
+    if fmt in _VECTOR_FORMATS:
+        return "native_vector"
+    raise ValueError(f"preflight has no writer family for format {fmt!r}")
+
+
+def _styles_finding(slot: str, decls: dict[str, Any], fmt: str) -> SlotFinding:
+    meta = _SLOTS_BY_ID[slot]
+    family = _slot_family(fmt)

_honored_props reads fmt in _RASTER_FORMATS for the same decision, so it can take the resolved family instead of re-deriving it.

Also applies to: 183-183

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/xy/styling/preflight.py` around lines 42 - 43, Update the
writer-family classification near _honored_props to resolve the format
explicitly against _RASTER_FORMATS and _VECTOR_FORMATS, rather than treating
every non-raster format as vector. Reject or otherwise fail clearly for formats
in neither set, then pass the resolved family through so _honored_props does not
re-derive it from fmt.

61-80: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

sources makes the frozen report mutable and unhashable.

StyleCompatibilityReport is frozen=True, but sources holds a plain dict, so a caller can mutate the report contents after it is returned. The generated __hash__ also fails on the dict field, so the report cannot go into a set or a dict key even though every other field is hashable. A Mapping field normalized at construction keeps the report read-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/xy/styling/preflight.py` around lines 61 - 80, Update
StyleCompatibilityReport.sources to use an immutable Mapping representation
rather than a plain dict, and normalize incoming mappings during construction so
callers cannot mutate the frozen report. Ensure the normalized sources value
remains hashable for generated __hash__ support while preserving existing source
lookups and defaults.

275-281: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Do not skip invalid style entries in preflight. Figure.class_names and Figure.chrome_styles are directly assignable, so callers can request a report before _dom_spec validates them. Report unknown slots and non-dict declarations explicitly, or validate them before routing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/xy/styling/preflight.py` around lines 275 - 281, Update the preflight
finding collection around Figure.class_names and Figure.chrome_styles so invalid
entries are not silently skipped before _dom_spec validation. Report unknown
slots and non-dict chrome style declarations explicitly, or route them through
the existing validation logic before creating _class_finding or _styles_finding
results.
python/xy/_figure.py (1)

2220-2237: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

style_compatibility_report returns Any at both public entry points. The preflight module returns StyleCompatibilityReport, and preflight imports Figure only under TYPE_CHECKING, so both call sites can import the report type under TYPE_CHECKING without a runtime import cycle. Callers then get type checking on lossless, findings, losses, and explain().

  • python/xy/_figure.py#L2220-L2237: add from .styling.preflight import StyleCompatibilityReport to the TYPE_CHECKING block and change the return annotation from Any to "StyleCompatibilityReport".
  • python/xy/components.py#L4172-L4191: add the same TYPE_CHECKING import and change Chart.style_compatibility_report to return "StyleCompatibilityReport".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@python/xy/_figure.py` around lines 2220 - 2237, Replace the public Any return
annotations for style_compatibility_report with StyleCompatibilityReport and add
the report-type import under TYPE_CHECKING in python/xy/_figure.py lines
2220-2237 and python/xy/components.py lines 4172-4191. Keep the runtime
preflight import structure unchanged and use the forward-reference annotation to
avoid import cycles.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@python/xy/_figure.py`:
- Around line 2220-2237: Replace the public Any return annotations for
style_compatibility_report with StyleCompatibilityReport and add the report-type
import under TYPE_CHECKING in python/xy/_figure.py lines 2220-2237 and
python/xy/components.py lines 4172-4191. Keep the runtime preflight import
structure unchanged and use the forward-reference annotation to avoid import
cycles.

In `@python/xy/styling/preflight.py`:
- Around line 42-43: Update the writer-family classification near _honored_props
to resolve the format explicitly against _RASTER_FORMATS and _VECTOR_FORMATS,
rather than treating every non-raster format as vector. Reject or otherwise fail
clearly for formats in neither set, then pass the resolved family through so
_honored_props does not re-derive it from fmt.
- Around line 61-80: Update StyleCompatibilityReport.sources to use an immutable
Mapping representation rather than a plain dict, and normalize incoming mappings
during construction so callers cannot mutate the frozen report. Ensure the
normalized sources value remains hashable for generated __hash__ support while
preserving existing source lookups and defaults.
- Around line 275-281: Update the preflight finding collection around
Figure.class_names and Figure.chrome_styles so invalid entries are not silently
skipped before _dom_spec validation. Report unknown slots and non-dict chrome
style declarations explicitly, or route them through the existing validation
logic before creating _class_finding or _styles_finding results.

In `@scripts/gen_capability_matrix.py`:
- Around line 66-74: Update the summary generation near
counts['slots_styleable_natively'] to derive the styles={...} slot count from
the capability registry instead of hard-coding “nine” and the root exception.
Reuse the registry’s native-support/channel data so the generated prose stays
synchronized with the table and total when slots change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b6ec73fb-cdf4-4123-a9de-5e3244115ad4

📥 Commits

Reviewing files that changed from the base of the PR and between 99eda6d and a99c037.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • docs/styling/capabilities.md
  • python/xy/_figure.py
  • python/xy/components.py
  • python/xy/styling/__init__.py
  • python/xy/styling/capabilities.py
  • python/xy/styling/preflight.py
  • scripts/gen_capability_matrix.py
  • spec/api/capability-matrix.md
  • spec/api/export.md
  • tests/test_capability_registry.py
  • tests/test_style_compatibility_report.py

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 12 files

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread python/xy/styling/preflight.py Outdated
Comment thread python/xy/styling/preflight.py
Comment thread tests/test_capability_registry.py
…ounts

Review follow-ups on the preflight change, all in the mirror-the-export
direction:

Browser-resolved targets now validate custom_css through the export path's
own _custom_css_block (type check, </style> and comment-sequence rejection),
in the export's own order — engine resolution first. The report could
previously call an export lossless that would refuse its stylesheet.

Malformed figure styling raises exactly like the export instead of being
skipped: class_names/chrome_styles are assignable, so a report can be
requested before the spec build validates them, and a silently omitted entry
would be a report hiding a declaration (§28). validate_dom_slots runs the
same check the spec build runs; a non-mapping declaration set is refused by
name.

The writer family is now classified from both format sets with a refusal
for anything unclassified, so a future format cannot silently be reported
against the vector subset. The generated summary derives the styles={...}
slot count from the registry instead of hard-coding "nine" (the
axis_style_keys lesson: prose cannot hold a count). Both public entry
points return the typed StyleCompatibilityReport rather than Any.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 7 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread python/xy/components.py
Comment thread python/xy/_figure.py
Comment thread python/xy/styling/preflight.py
@Alek99

Alek99 commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Consolidated into #455 (single-PR review requested) — same commits including the review follow-ups; the finding dispositions are listed in #455's description.

@Alek99 Alek99 closed this Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant